﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace HeritageAcademy.Library.Controls
{
    public class ElementRenderer : Decorator
    {
        #region Parameters 
        public static readonly RoutedEvent ContentRenderedEvent = EventManager.RegisterRoutedEvent("ContentRendered", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ElementRenderer));
        bool IsChildVisible = true;
        bool IsRenderImageValid;
        ImageSource RenderImage;
        #endregion 

        #region Properties 
        public bool AutoUpdateRenderOnArrange { get; set; }
        #endregion Properties 

        #region Events 
        public event RoutedEventHandler ContentRendered
        {
            add { AddHandler(ContentRenderedEvent, value); }
            remove { RemoveHandler(ContentRenderedEvent, value); }
        }
        #endregion Events 

        #region Public Methods 
        public void InvalidateRenderedContent()
        {
            IsRenderImageValid = false;
            InvalidateVisual();
        }
        #endregion Public Methods 

        #region Protected Methods 
        protected override Size ArrangeOverride(Size arrangeSize)
        {
            if (AutoUpdateRenderOnArrange)
            {
                IsRenderImageValid = false;
            }
            return base.ArrangeOverride(arrangeSize);
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            if (!IsLive)
            {
                if (!IsRenderImageValid)
                {
                    if (!IsChildVisible)
                    {
                        // this will force a new render so we just return
                        Child.Visibility = Visibility.Visible;
                        IsChildVisible = true;
                        return;
                    }

                    RenderContentToBitmap();

                    // this needs to be hidden, not collapsed, so that we don't continuously
                    // invalidate layout every time we render
                    Child.Visibility = Visibility.Hidden;
                    IsChildVisible = false;
                    IsRenderImageValid = true;
                }

                base.OnRender(drawingContext);


                if (RenderImage == null) return;
                drawingContext.DrawImage(RenderImage, new Rect(0, 0, RenderImage.Width, RenderImage.Height));
            }
            else
            {
                Child.Visibility = Visibility.Visible;
                base.OnRender(drawingContext);
            }
        }

        #endregion Protected Methods 

        #region Private Methods 
        private static void GetCurrentDPI(out double x, out double y)
        {
            Matrix m;
            try
            {
                m = PresentationSource.FromVisual(Application.Current.MainWindow).CompositionTarget.TransformToDevice;
            }
            catch (Exception)
            {
                throw new NullReferenceException();
            }
            
            x = 96 / m.M11;
            y = 96 / m.M22;
        }

        void RaiseContentRenderedEvent()
        {
            var newEventArgs = new RoutedEventArgs(ContentRenderedEvent);
            RaiseEvent(newEventArgs);
        }

        private void RenderContentToBitmap()
        {
            // get DPI values (x, y)
            double x;
            double y;
            GetCurrentDPI(out x, out y);

            var bounds = VisualTreeHelper.GetDescendantBounds(this);
            var r = new RenderTargetBitmap((int)(ActualWidth * x / 96), (int)(ActualHeight * y / 96), (int)x, (int)y, PixelFormats.Pbgra32);

            if (UseVisualBrushRendering)
            {
                var dv = new DrawingVisual();
                using (var ctx = dv.RenderOpen())
                {
                    var vb = new VisualBrush(this);

                    ctx.DrawRectangle(vb, null, new Rect(new Point(), bounds.Size));
                }
                r.Render(dv);
            }
            else
            {
                r.Render(this);
            }

            RenderImage = r;

            RaiseContentRenderedEvent();
        }
        #endregion Private Methods 

        #region UseVisualBrushRendering
        /// <summary>
        /// UseVisualBrushRendering Dependency Property
        /// </summary>
        public static readonly DependencyProperty UseVisualBrushRenderingProperty =
            DependencyProperty.Register("UseVisualBrushRendering", typeof(bool), typeof(ElementRenderer),
                new FrameworkPropertyMetadata(false));

        /// <summary>
        /// Gets or sets the UseVisualBrushRendering property.  This dependency property 
        /// indicates that a visual brush should be created prior to rendering the content. 
        /// This can help with certain sizing and bounds issues.
        /// </summary>
        public bool UseVisualBrushRendering
        {
            get { return (bool)GetValue(UseVisualBrushRenderingProperty); }
            set { SetValue(UseVisualBrushRenderingProperty, value); }
        }
        #endregion

        #region IsLive
        public static readonly DependencyProperty IsLiveProperty =
            DependencyProperty.Register("IsLive", typeof(bool), typeof(ElementRenderer),
                new FrameworkPropertyMetadata(false,
                    FrameworkPropertyMetadataOptions.AffectsRender));

        public bool IsLive
        {
            get { return (bool)GetValue(IsLiveProperty); }
            set { SetValue(IsLiveProperty, value); }
        }
        #endregion
    }
}
